package bhoost.shell;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Hashtable;
import java.util.LinkedList;

import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smackx.muc.DiscussionHistory;
import org.jivesoftware.smackx.muc.MultiUserChat;
import org.jivesoftware.smackx.packet.DelayInformation;

import bhoost.shell.smack_providers.BytestreamsProvider;
import bhoost.shell.smack_providers.ChatStateExtension;
import bhoost.shell.smack_providers.DataFormProvider;
import bhoost.shell.smack_providers.DelayInformationProvider;
import bhoost.shell.smack_providers.DiscoverInfoProvider;
import bhoost.shell.smack_providers.DiscoverItemsProvider;
import bhoost.shell.smack_providers.GroupChatInvitation;
import bhoost.shell.smack_providers.IBBProviders;
import bhoost.shell.smack_providers.LastActivity;
import bhoost.shell.smack_providers.MUCAdminProvider;
import bhoost.shell.smack_providers.MUCOwnerProvider;
import bhoost.shell.smack_providers.MUCUserProvider;
import bhoost.shell.smack_providers.MessageEventProvider;
import bhoost.shell.smack_providers.MultipleAddressesProvider;
import bhoost.shell.smack_providers.OfflineMessageInfo;
import bhoost.shell.smack_providers.OfflineMessageRequest;
import bhoost.shell.smack_providers.PrivacyProvider;
import bhoost.shell.smack_providers.PrivateDataManager;
import bhoost.shell.smack_providers.RosterExchangeProvider;
import bhoost.shell.smack_providers.SharedGroupsInfo;
import bhoost.shell.smack_providers.StreamInitiationProvider;
import bhoost.shell.smack_providers.UserSearch;
import bhoost.shell.smack_providers.VCardProvider;
import bhoost.shell.smack_providers.Version;
import bhoost.shell.smack_providers.XHTMLExtensionProvider;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.IBinder;
import android.util.Log;
import android.util.Time;

public class SmackService extends Service {

    final String TAG = "SMACK_SERVICE";
    static final String resource = "marco-android";
    static String domain; //= "conference.lablz.com";
    static String chatService; //= "conference";
    NotificationManager nm;
    XMPPConnection connection;
//    XMPPConnection connectionTemp;
    Hashtable<String, MultiUserChat> chats;
    MultiUserChat currentChat;
    static String username;
    static String password;
    Hashtable<String, LinkedList<XMPPMessage>> privateMessageQueues;
    Hashtable<String, LinkedList<XMPPMessage>> chatroomMessageQueues;
    LinkedList<XMPPMessage> currentChatQueue;
    private static final int MAX_HISTORY = 10;
//    ISmackServiceCallback callback;
    public static final int CHATROOM_MESSAGE = 1;
    public static final int PRIVATE_MESSAGE = 2;
    public static final String PRIVATE_MESSAGE_ACTION = "com.test1.SmackService.PRIVATE_MESSAGE_ACTION";
    public static final String CHATROOM_MESSAGE_ACTION = "com.test1.SmackService.CHATROOM_MESSAGE_ACTION";
    public static final String NOTIFICATION_ACTION = "com.test1.SmackService.NOTIFICATION_ACTION";
//    private boolean initCalled = false;
    private boolean keepQueues = false;
    private static boolean bhoostOutOfFocus = false;
    private boolean showNotifications = false;

    @Override
    protected void onCreate() {
      super.onCreate();
      logD("onCreate()");
      init();
      nm = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
      setSmackProviders();
    }

    private void init() {
      chatroomMessageQueues = new Hashtable<String, LinkedList<XMPPMessage>>();
      privateMessageQueues = new Hashtable<String, LinkedList<XMPPMessage>>();
      chats = new Hashtable<String, MultiUserChat>();
      currentChat = null;
      currentChatQueue = null;
    }

    @Override
    public void onDestroy() {
      logD("onDestroy()");
      super.onDestroy();

      for (MultiUserChat chat: chats.values()) {
        try {
          if (chat != null && chat.isJoined())
            chat.leave();
        } catch (Exception e) {
          logD("failed to destroy: " + e.getMessage());
        }
      }

      try {
        if (connection != null && connection.isConnected())
          connection.disconnect();
        connection = null;
      } catch (Exception e) {
        logD("failed to disconnect");
      }
    }

//    private Runnable mTask = new Runnable() {
//      public void run() {
//        try {
//          logD("run()");
//          setUp();
//        } catch (Throwable e) {
//          logD("failed in run(): " + e.getMessage());
//        }
//      }
//    };

    public long getTimestamp(Packet packet) {
      logD("trying to get time from packet");
      Time time = (Time) packet.getExtension("query", "jabber:iq:time");
      if (time != null) {
        logD("got time from time extension: " + time.toMillis(true));
        return time.toMillis(true);
      }
      DelayInformation delay = (DelayInformation) packet.getExtension("x", "jabber:x:delay");
      if (delay != null) {
        logD("got time from delay extension: " + delay.getStamp().getTime());
        return delay.getStamp().getTime();
      }

      return -1;
    }

//    private final Handler handler = new Handler() {
//      @Override
//      public void handleMessage(android.os.Message msg) {
//        switch (msg.what) {
//          case CHATROOM_MESSAGE:
//            logD("forwarding private message to Bhoost");
//            if (!(msg.obj instanceof XMPPMessage))
//              super.handleMessage(msg);
//            XMPPMessage xmpp = (XMPPMessage)msg.obj;
//            try {
//              callback.receivedMessage(xmpp);
//            } catch (DeadObjectException d) {
//              logD("dead object on chatroom msg callback: " + d.getMessage());
//            }
//            break;
//          case PRIVATE_MESSAGE:
//            logD("forwarding private message to Bhoost");
//            if (!(msg.obj instanceof XMPPMessage))
//              super.handleMessage(msg);
//            XMPPMessage xmpp1 = (XMPPMessage)msg.obj;
//            try {
//              callback.receivedMessage(xmpp1);
//            } catch (DeadObjectException d) {
//              logD("dead object on private msg callback: " + d.getMessage());
//            }
//            break;
//          default:
//              super.handleMessage(msg);
//        }
//      }
//    };

    /**
     * ISmackService is defined through IDL
     */
    private final ISmackService.Stub mBinder = new ISmackService.Stub() {
//      private Message current;

//      public void registerCallback(ISmackServiceCallback cb) {
//        if (cb != null) callback = cb;
//      }
//      public void unregisterCallback(ISmackServiceCallback cb) {
//          if (cb != null) callback = null;
//      }

      public void setHost(String domainStr) {
        domain = domainStr;
      }

      public void clientPaused(boolean paused) {
        logD("client paused: " + paused);
        keepQueues = paused;
        SmackService.bhoostOutOfFocus = paused;
        showNotifications = paused;
      }

      /**
       * send all messages in queues to Bhoost
       */
      public void sendSavedMessages() {
        logD("sending saved messages");
        if (chatroomMessageQueues == null)
          return;

        for (LinkedList<XMPPMessage> queue: chatroomMessageQueues.values()) {
          for (XMPPMessage msg: queue) {
            sendXMPPMessage(msg, false);
          }
        }
        for (LinkedList<XMPPMessage> queue: privateMessageQueues.values()) {
          for (XMPPMessage msg: queue) {
            sendXMPPMessage(msg, true);
          }
        }
      }

      public void clearQueues() {
        logD("clearing queues");
        if (chatroomMessageQueues != null)
          chatroomMessageQueues.clear();
      }

      public void login(String name, String pwd) {
        if (domain == null) {
          logD("couldn't login, host was not set");
          return;
        }

        logD("trying to log in as " + name + ", domain is: " + domain);

        if (connection != null && connection.isAuthenticated()) {
          if (!username.equals(name)) {
            connection.disconnect();
            connection = null;
            init();
          }
          else
            return;
        }

        username = name;
        password = pwd;

        if (connection == null || !connection.isConnected())
          connect();

        if (!connection.isAuthenticated()) {
          try {
            connection.login(username, password, resource);
            logD("logged in as " + username);
          } catch (XMPPException x) {
            logD("couldn't connect or login: " + x.getMessage());
          }
        }

        PacketFilter filter = new PacketTypeFilter(Message.class);
        PacketListener myListener = new PacketListener() {
          public void processPacket(Packet packet) {
            Message message = (Message)packet;
            if (message.getType() == Message.Type.groupchat) {
              logD("received message - but it's a groupchat message");
              return;
            }

            String to = message.getTo();
            String from = message.getFrom();
            logD("received message for: " + to + ", body = " + message.getBody() + ", from: " + from);

            if (from == null || from.toLowerCase().equals(username.toLowerCase() + "@" + domain.toLowerCase() + "/" + resource.toLowerCase()))
              return;  // it's an echo

            if (to == null || !to.toLowerCase().startsWith(username.toLowerCase() + "@" + domain.toLowerCase()))
              return;

            if (to.contains("/") && !to.substring(to.lastIndexOf("/") + 1).toLowerCase().equals(resource.toLowerCase()))
              return;

            if (message.getBody() == null || message.getBody().trim().length() == 0) {
              return;
            }

            XMPPMessage xmppMsg = new XMPPMessage();
            xmppMsg.body = message.getBody();
            xmppMsg.sender = message.getFrom();
            xmppMsg.receiver = message.getTo();
            xmppMsg.time = getTime(message);
            if (xmppMsg.time == -1)
              xmppMsg.time = System.currentTimeMillis();
            Object obj = message.getProperty("url");
            if (obj != null && obj instanceof String)
              xmppMsg.url = (String)obj;
            xmppMsg.type = message.getType().toString();
            if (xmppMsg.url == null && showNotifications) {
              sendNotification(xmppMsg);
            }

            if (!keepQueues)
              sendXMPPMessage(xmppMsg, true);
            else if (xmppMsg.sender != null) {
              if (privateMessageQueues.containsKey(xmppMsg.sender)) {
                LinkedList<XMPPMessage> privateMessageQueue = privateMessageQueues.get(xmppMsg.sender);
                privateMessageQueue.add(xmppMsg);
              }
              else {
                LinkedList<XMPPMessage> privateMessageQueue = new LinkedList<XMPPMessage>();
                privateMessageQueue.add(xmppMsg);
                privateMessageQueues.put(xmppMsg.sender, privateMessageQueue);
              }
            }
          }

        };
        connection.addPacketListener(myListener, filter);
        PacketFilter presenceFilter = new PacketTypeFilter(Presence.class);
        connection.addPacketListener(new PacketListener() {
          public void processPacket(Packet packet) {
            if (!(packet instanceof Presence))
              return;

            Presence presence = (Presence) packet;
            String from = presence.getFrom();
            String to = presence.getTo();
            if (to == null || !to.toLowerCase().startsWith(username.toLowerCase() + "@" + domain.toLowerCase()))
              return;

            if (to.contains("/") && !to.substring(to.lastIndexOf("/") + 1).toLowerCase().equals(resource.toLowerCase()))
              return;

            String presenceType = presence.getType().toString();
            String messageType = "presence";
//            logD("presence: " + presenceType + ", from: " + from);
            if (from == null || presenceType == null)
              return;

            XMPPMessage xmppMsg = new XMPPMessage();
            xmppMsg.type = messageType;
            xmppMsg.body = presenceType;
            xmppMsg.sender = from;

            if (!keepQueues)
              sendXMPPMessage(xmppMsg, true);
            else if (xmppMsg.sender != null) {
              if (privateMessageQueues.containsKey(xmppMsg.sender)) {
                LinkedList<XMPPMessage> privateMessageQueue = privateMessageQueues.get(xmppMsg.sender);
                privateMessageQueue.add(xmppMsg);
              }
              else {
                LinkedList<XMPPMessage> privateMessageQueue = new LinkedList<XMPPMessage>();
                privateMessageQueue.add(xmppMsg);
                privateMessageQueues.put(xmppMsg.sender, privateMessageQueue);
              }
            }
          }
        }, presenceFilter);
      }

//      public void init(long lastMessageTime) {
//        logD("init()");
//        initCalled = true;
//        if (currentChatQueue == null)
//          return;
//
//        synchronized (currentChatQueue) {
//          int size = currentChatQueue.size();
//          int index = 0;
//          while (index < size) {
//            XMPPMessage m = currentChatQueue.get(index);
//            long millis = m.time;
//            if (millis <= lastMessageTime) {
//              currentChatQueue.remove(index);
//              size = currentChatQueue.size();
//              continue;
//            }
//
//            index++;
//          }
//
//          for (XMPPMessage m: currentChatQueue) {
//            Intent privateMessage = new Intent(SmackService.PRIVATE_MESSAGE_ACTION);
//            Bundle stuff = new Bundle();
//            stuff.putParcelable("message", m);
//            privateMessage.putExtras(stuff);
//            broadcastIntent(privateMessage);
//          }
//        }
//      }

      public void sendMessage(String text, String to) {
        logD("sendMessage(" + text + ", " + to + ")");

        if (currentChat == null && (to == null || to.trim().length() == 0)) {
          logD("attempted to send message without being in a chatroom or without specifying recipient");
          return;
        }

        try {
          if (text == null || text.trim().length() == 0)
            return;

          if (connection == null || !connection.isConnected()) {
            logD("sendMessage - not connected, running setUp()");
            connect();
          }

          try {
            Message msg;
            if (to == null || to.trim().length() == 0) {
              msg = currentChat.createMessage();
              msg.setType(Message.Type.groupchat);
            }
            else {
              msg = new Message();
              msg.setType(Message.Type.chat);
            }
            msg.setBody(text);
//            msg.setFrom(username);
            msg.setProperty("time", new Long(System.currentTimeMillis()));
            if (to != null && to.trim().length() != 0) {
              if (!to.contains("@"))
                to += "@" + domain;
//              if (!to.contains("/"))
//                to += "/" + resource;
              msg.setTo(to);
              connection.sendPacket((Packet)msg);
              logD("sending from connection");
            }
            else {
              currentChat.sendMessage(msg);
              logD("sending from chatroom");
            }
          } catch (XMPPException x) {
            logD("failed in sendMessage - 1: " + x.getXMPPError().getCode() + ", " + x.getMessage());
          }
        } catch (Throwable e) {
          logD("failed in sendMessage - 2: " + e.getMessage());
        }
      }

//      public String getSender() {
//        if (current == null)
//          return null;
//
//        String from = current.getFrom();
//        int slashIndex = from.indexOf("/");
//        logD("getSender() = " + current.getFrom());
//        if (slashIndex == -1)
//          return from;
//        else
//          return from.substring(slashIndex + 1);
//      }
//
//      public String getBody() {
//        if (current == null)
//          return null;
//
//        logD("getBody() = " + current.getBody());
//        return current.getBody();
//      }

      public String getChatRoom() {
        if (currentChat == null)
          return null;
        return currentChat.getNickname();
      }

      public void setChatRoom(String nickname) {
        logD("setChatRoom(" + nickname + ")");
        if (connection == null)
          return;

        LinkedList<XMPPMessage> queue = chatroomMessageQueues.get(nickname);
        if (queue == null) {
          queue = new LinkedList<XMPPMessage>();
          chatroomMessageQueues.put(nickname, queue);
        }

        currentChatQueue = queue;

        MultiUserChat chat = chats.get(nickname);
        if (chat == null) {
          chat = makeNewChat(nickname, queue);
          chats.put(nickname, chat);
        }

        currentChat = chat;

        // join the room
        try {
          DiscussionHistory dh = new DiscussionHistory();
          dh.setMaxStanzas(0);
          int numTries = 5;
          for (int i = 0; i < numTries && !chat.isJoined(); i++) {
            logD("attempting to join as " + username + " with password " + password + ", try " + i);
            chat.join(username, password, dh, 5000); // 5 seconds timeout
          }
          logD("joined");
        } catch (XMPPException x) {
          logD("failed to join: " + x.getMessage());
        }
      }

//      public void cancelNotification() {
//        logD("cancelNotification()");
//        if (nm != null)
//          SmackService.this.nm.cancel(R.string.notification);
//      }

      private MultiUserChat makeNewChat(String room, final LinkedList<XMPPMessage> queue) {
        logD("making new chatroom");
        MultiUserChat chat = new MultiUserChat(connection, room);
        chat.addMessageListener(new PacketListener() {
          public void processPacket(Packet packet) {
            Message message = (Message) packet;
            if (message.getBody() == null || message.getBody().trim().length() == 0) {
              logD("null body: type = " + message.getType().toString() + ", to = " + message.getTo() + ", from = " + message.getFrom());
              return;
            }

            String from = message.getFrom();
            logD("received message in muc from " + message.getFrom() + ": " + message.getBody());
            if (from == null)
              return;

            int slashIndex = from.indexOf("/");
            if (slashIndex != -1 && from.toLowerCase().substring(slashIndex + 1).equals(username.toLowerCase())) {
              logD("not forwarding - echo message");
              return; // echo
            }

            XMPPMessage xmppMsg = new XMPPMessage();
            xmppMsg.body = message.getBody();
            xmppMsg.sender = message.getFrom();
            xmppMsg.chatroom = currentChat.getRoom();
            xmppMsg.receiver = null;
            xmppMsg.time = getTime(message);
            if (xmppMsg.time == -1)
              xmppMsg.time = System.currentTimeMillis();
            xmppMsg.type = message.getType().toString();
//                msg.what = CHATROOM_MESSAGE;
//                msg.obj = xmppMsg;
//                logD("successfully handled: " + handler.sendMessage(msg));
//              if (initCalled) {
            if (showNotifications)
              sendNotification(xmppMsg);

            if (keepQueues)
              queue.add(xmppMsg);
            else
              sendXMPPMessage(xmppMsg, false);
          }
        });
        return chat;
      }

//      public String getChatRoom() {
//        logD("getChatRoom()");
//        if (currentChat != null)
//          return currentChat.getNickname();
//
//        return null;
//      }
    };


    @Override
    public IBinder onBind(Intent intent) {
      if (mBinder != null) {
        try {
          mBinder.clientPaused(false);
        } catch (DeadObjectException d) {
          logD("deadobject in on bind");
        }
      }
      logD("onBind()");
//      Bundle extras = intent.getExtras();
//      username = extras.getString("username");
//      password = extras.getString("password");
//      Thread thr = new Thread(null, mTask, "SmackService");
//      thr.start();
      return mBinder;
    }

//    private void handleCaching(XMPPMessage xmppMsg) {
//      String url = xmppMsg.url;
//      String method = xmppMsg.method;
//      String data = xmppMsg.body;
//      if (url == null || method == null || data == null)
//        return;
//
//      method = method.toLowerCase().trim();
//
//      if (method.equals("put")) {
//        CacheManager.put(url, data);
//      }
//      else if (method.equals("get")) {
//        sendXMPPMessage(xmppMsg);
//      }
//      else if (method.equals("")) {
//
//      }
//    }
    public void sendNotification(XMPPMessage xmppMsg) {
      logD("sendNotification()");
      if (xmppMsg.sender == null)
        return;

      Intent i = new Intent(this, Bhoost.class);
      String shortName = xmppMsg.sender.contains("@") ? xmppMsg.sender.substring(0, xmppMsg.sender.indexOf("@")) : xmppMsg.sender;
      Notification notification = new Notification(
          this, // our context
          R.drawable.incoming_message, // the icon for the status bar
          shortName + ": " + xmppMsg.body, // the text to display in the ticker
          0, // the timestamp for the notification
          shortName + " says: ", // the title for the notification
          xmppMsg.body, // the details to display in the notification
          i, // the contentIntent (see above)
          R.drawable.logo_large, // the app icon
          getResources().getString(R.string.app_name), // the name of the app
          i); // intent for when you click on icon

      nm.notify(R.string.notification, notification);
      logD("notification sent");
    }

    public void connect() {
      try {
        if (connection == null)
          configureConnection();
        while (!connection.isConnected()) {
          for (int i = 0; i < 5; i++) {
            logD("not connected");
            try {
              connection.connect();
              logD("connected");
            } catch (XMPPException x) {
              logD("couldn't connect: " + x.getMessage());
            }
            if (connection.isConnected())
              return;
          }
          Thread.sleep(5000);
        }
      } catch (Throwable e) {
        logD("failed in setUp(): " + e.getMessage());
      }
    }

    public long getTime(Message msg) {
      logD("trying to get time from message, message null: " + (msg == null));

      if (msg == null)
        return -1;

      Object obj = msg.getProperty("time");
      if (obj == null)
        return -1;

      if (obj instanceof String) {
        String time = (String)obj;
        if (time.trim().length() == 0)
          return -1;

        SimpleDateFormat sdf = new SimpleDateFormat();
        try {
          Date date = sdf.parse(time);
          return date.getTime();
        } catch (ParseException p) {
          return -1;
        }
      }

      if (!(obj instanceof Long))
        return -1;

      Long milliseconds = (Long)obj;
      if (milliseconds == null)
        return -1;

      long millis = milliseconds.longValue();
      return millis;
    }

    private void configureConnection() {
      logD("configuring connection");
      XMPPConnection.DEBUG_ENABLED = false;
      ConnectionConfiguration config = new ConnectionConfiguration(domain, 5222);
      config.setSASLAuthenticationEnabled(false);
      config.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled);
      connection = new XMPPConnection(config);
      logD("configured connection");
    }

    private void setSmackProviders() {
      logD("setting smack providers");
      ProviderManager pm = ProviderManager.getInstance();
      pm.addExtensionProvider("x", "jabber:x:roster",                               new RosterExchangeProvider());
      pm.addExtensionProvider("x", "jabber:x:event",                                new MessageEventProvider());
      pm.addExtensionProvider("active", "http://jabber.org/protocol/chatstates",    new ChatStateExtension.Provider());
      pm.addExtensionProvider("composing", "http://jabber.org/protocol/chatstates", new ChatStateExtension.Provider());
      pm.addExtensionProvider("paused", "http://jabber.org/protocol/chatstates",    new ChatStateExtension.Provider());
      pm.addExtensionProvider("inactive", "http://jabber.org/protocol/chatstates",  new ChatStateExtension.Provider());
      pm.addExtensionProvider("gone", "http://jabber.org/protocol/chatstates",      new ChatStateExtension.Provider());
      pm.addExtensionProvider("html", "http://jabber.org/protocol/xhtml-im",        new XHTMLExtensionProvider());
      pm.addExtensionProvider("x", "jabber:x:conference",                           new GroupChatInvitation.Provider());
      pm.addExtensionProvider("x", "jabber:x:data",                                 new DataFormProvider());
      pm.addExtensionProvider("x", "http://jabber.org/protocol/muc#user",           new MUCUserProvider());
      pm.addExtensionProvider("x", "jabber:x:delay",                                new DelayInformationProvider());
      pm.addExtensionProvider("offline", "http://jabber.org/protocol/offline",      new OfflineMessageInfo.Provider());
      pm.addExtensionProvider("addresses", "http://jabber.org/protocol/address",    new MultipleAddressesProvider());
      pm.addExtensionProvider("data", "http://jabber.org/protocol/ibb",             new IBBProviders.Data());
      pm.addIQProvider("query", "jabber:iq:private",                                new PrivateDataManager.PrivateDataIQProvider());
      pm.addIQProvider("query", "jabber:iq:time",                                   bhoost.shell.smack_providers.Time.class);
      pm.addIQProvider("query", "http://jabber.org/protocol/disco#items",           new DiscoverItemsProvider());
      pm.addIQProvider("query", "http://jabber.org/protocol/disco#info",            new DiscoverInfoProvider());
      pm.addIQProvider("query", "http://jabber.org/protocol/muc#admin",             new MUCAdminProvider());
      pm.addIQProvider("query", "http://jabber.org/protocol/muc#owner",             new MUCOwnerProvider());
      pm.addIQProvider("query", "jabber:iq:version",                                Version.class);
      pm.addIQProvider("vCard", "vcard-temp",                                       new VCardProvider());
      pm.addIQProvider("offline", "http://jabber.org/protocol/offline",             new OfflineMessageRequest.Provider());
      pm.addIQProvider("query", "jabber:iq:last",                                   new LastActivity.Provider());
      pm.addIQProvider("query", "jabber:iq:search",                                 new UserSearch.Provider());
      pm.addIQProvider("sharedgroup", "http://www.jivesoftware.org/protocol/sharedgroup", new SharedGroupsInfo.Provider());
      pm.addIQProvider("si", "http://jabber.org/protocol/si",                       new StreamInitiationProvider());
      pm.addIQProvider("query", "http://jabber.org/protocol/bytestreams",           new BytestreamsProvider());
      pm.addIQProvider("open", "http://jabber.org/protocol/ibb",                    new IBBProviders.Open());
      pm.addIQProvider("close", "http://jabber.org/protocol/ibb",                   new IBBProviders.Close());
      pm.addIQProvider("query", "jabber:iq:privacy",                                new PrivacyProvider());
    }

    public void sendXMPPMessage(XMPPMessage xmppMsg, boolean privateMsg) {
      Intent privateMessage = new Intent(privateMsg ? SmackService.PRIVATE_MESSAGE_ACTION : SmackService.CHATROOM_MESSAGE_ACTION);
      Bundle stuff = new Bundle();
      stuff.putParcelable("message", xmppMsg);
      privateMessage.putExtras(stuff);
      broadcastIntent(privateMessage);
      logD("sent to bhoost, private = " + privateMsg);
    }

    public void logD(String msg) {
      Log.d(TAG, msg);
    }
}
